--[[ Copyright 2012, Chris Ho                               ]]--
--[[ MIT License (Please read the LICENSE file for details) ]]--

Use("common/Const");
Use("common/Log");
Use("common/Position");
Use("common/Creature");
Use("common/Skill");

-- The task context is a snapshot of certain pet states. It translates
-- into direct calls to iRO's API. Note that the task functions should
-- not alter the data structure directly!
--
-- This file also includes the two basic tasks: Go (moveement) and
-- Do (execute targeted skill/attack).
--
-- progress: progress state
-- id: who is responsible for the task
-- origin: where the task started
-- time: when the task started
-- hp: hit points at task creation
-- sp: skill points at task creation

-- destination: desired destination
-- target: desired target
-- skill: skill necessary for the task (if any)
Task = {};

-- Generic Progress Codes
PROGRESS_FAILURE = -1;
PROGRESS_NEW = 0;
PROGRESS_ACTIVE = 1;
PROGRESS_PAUSED = 2;
PROGRESS_SUCCESS = 3;

function Task.Create(_pet)
  local instance = {};

  instance.progress = PROGRESS_NEW;
  instance.id = _pet.id;
  instance.origin = Position.Find(_pet.id);
  instance.time = GetTick();
  instance.hp = GetV(V_HP, _pet.id);
  instance.sp = GetV(V_SP, _pet.id);

  --[[ Process Tasks ]]--
  -- We also update our internal progress code (regardless of its original
  -- value). We return the task that must execute after this one executes
  -- (which may be the same task).
  --
  -- By default, we stop and do nothing.
  function instance:Execute()
    Info("Task.Execute() standing still");
    local motion = GetV(V_MOTION, self.id);
    local here = Position.Find(self.id);

    if (motion ~=  MOTION_STAND) then
      Move(self.id, here.x, here.y);
    end

    return self:Progress(PROGRESS_SUCCESS);
  end


  --[[ Change progress (return final progress) ]]--
  function instance:Progress(_code)
    self.progress = _code;
    return self.progress;
  end

  --[[ Prematurely end a task. ]]--
  -- TODO: return the next task in a sequence?
  function instance:Terminate()
    if (self.progress ~= PROGRESS_SUCCESS) then self.progress = PROGRESS_FAILURE end;
  end


  --[[ Move a squarae in a random direction ]]--
  -- TODO
  function instance:Wander()
    Debug("Task.Wander()");
    local position = Position.Find(self.id);

    -- Move a random distance in a random direction.
    position:Disperse(math.random(1, math.max(LEASH / 5, 2)));

    -- Wander around a few spaces.
    Debug("[Move] " .. self.id .. ": " .. position.x .. "," .. position.y);
    Move(self.id, position.x, position.y);
  end

  --[[ Move to a position ]]--
  function instance:Move(_destination)
    Debug("Task.Move()");
    local from = Position.Find(self.id);

    -- Make sure the destination is in visibility range.
    if (Position.Measure(from, _destination) >= VISIBILITY) then
      Alert("Destination is too far away (" .. from.x .. "," .. from.y .. " - " .. _destination.x .. "," .. _destination.y .. ")");
      return PROGRESS_FAILURE;
    end

    -- Move if we're not at the destination.
    if ((_destination.x ~= from.x) or (_destination.y ~= from.y)) then
      Debug("[Move] " .. self.id .. ": " .. _destination.x .. "," .. _destination.y);
      Move(self.id, _destination.x, _destination.y);

--[[
      if ((GetV(V_MOTION, self.id) == MOTION_STAND) and ((from.x ~= _destination.x) or (from.y ~= _destination.y))) then
        -- Movement failed. Were we blocked? If so, let's try another
        -- way (random).
        Alert("Refused to move!");
        self:Wander();
      end
]]--
      return PROGRESS_ACTIVE;
    end

    -- Arrived.
    return PROGRESS_SUCCESS;
  end

  --[[ Try to get within the given range of destination ]]--
  function instance:Approach(_destination, _range)
    Debug("Task.Approach() " .. _destination.x .. "," .. _destination.y .. " at " .. _range);
    local from = Position.Find(self.id);

    -- If we're out of range, try to move to range.
    if (Position.Measure(from, _destination) > _range) then
      local desired = from:Route(_destination, _range - 0.5);

      -- Make sure target is in visibilit range. When we move, we move to
      -- the range limit.
      if (Position.Measure(from, desired) < VISIBILITY) then
        Debug("[Move] " .. self.id .. ": " .. desired.x .. "," .. desired.y);
	Move(self.id, desired.x, desired.y);

--[[
        if ((GetV(V_MOTION, self.id) == MOTION_STAND) and ((from.x ~= desired.x) or (from.y ~= desired.y))) then
          -- Movement failed. Were we blocked? If so, let's try another
          -- way (random).
          Alert("Refused to advance!");
          self:Wander();
        end
]]--
        return PROGRESS_ACTIVE;
      else
        Alert("Can't advance into " .. desired.x .. "," .. desired.y .. "!");
        return PROGRESS_FAILURE;
      end
    end

    -- Rendezvous Complete. Stop here.
    return PROGRESS_SUCCESS;
  end

  --[[ Get to at least the given range of the destination ]]--
  function instance:Withdraw(_destination, _range)
    Debug("Task.Withdraw()");
    local from = Position.Find(self.id);

    -- If we're in range, move at least to the edge.
    if (Position.Measure(from, _destination) < _range) then
      local desired = from:Route(_destination, _range + 0.5);

      -- Make sure target is in visibilit range. When we move, we move to
      -- the range minimum.
      if (Position.Measure(from, desired) < VISIBILITY) then
        Debug("[Move] " .. self.id .. ": " .. desired.x .. "," .. desired.y);
	Move(self.id, desired.x, desired.y);

--[[
        if ((GetV(V_MOTION, self.id) == MOTION_STAND) and ((from.x ~= desired.x) or (from.y ~= desired.y))) then
          -- Movement failed. Were we blocked? If so, let's try another
          -- way (random).
          Alert("Refused to retreat!");
          self:Wander();
        end
]]--
        return PROGRESS_ACTIVE;
      else
        Alert("Can't retreat to " .. desired.x .. "," .. desired.y .. "!");
        return PROGRESS_FAILURE;
      end
    end

    -- Withdrawal Complete. Stop here.
    return PROGRESS_SUCCESS;
  end

  --[[ Use a skill/attack on a specific location ]]--
  function instance:Apply(_skill, _destination)
    Debug("Task.Apply()");

    local from = Position.Find(self.id);
    local distance = Position.Measure(from, _destination);

    -- If it's too far away, give up.
    if (distance >= VISIBILITY) then
      Alert("Destination is too far away. (" .. from.x .. "," .. from.y .. " - " .. _destination.x .. "," .. _destination.y .. ")");
      return PROGRESS_FAILURE;
    end

    -- Apply skill if we're in range.
    local range = _skill:Estimate();
    if (distance <= range) then
      Debug("[SkillGround] " .. self.id .. ": " .. _skill.code .. "#" .. _skill.level .. "@" .. _destination.x .. "," .. _destination.y .. " (range " .. range .. ")");
      SkillGround(self.id, _skill.level, _skill.code, _destination.x, _destination.y);
      return PROGRESS_SUCCESS;
    end

    -- We're not in range.
    return PROGRESS_FAILURE;
  end

  --[[ Use a skill/attack on a specific target ]]--
  function instance:Target(_skill, _target)
    Debug("Task.Target()");

    local from = Position.Find(self.id);
    local to = Position.Find(_target);
    local distance = Position.Measure(from, to);

    -- If it's too far away, give up.
    if (distance >= VISIBILITY) then
      Alert("Target is too far away. (" .. from.x .. "," .. from.y .. " - " .. to.x .. "," .. to.y .. ")");
      return PROGRESS_FAILURE;
    end

    -- Act if we're in range.
    local range = _skill:Estimate();
    if (_skill.code == 0) then
      -- Basic Attack:
      if (distance <= range) then
        Debug("[Attack] " .. self.id .. ": " .. _target .. " (range " .. range .. ")");
        Attack(self.id, _target);
	return PROGRESS_SUCCESS;
      end
    else
      -- Skill Attack:
      if (distance <= range) then
        Debug("[SkillObject] " .. self.id .. ": " .. _skill.code .. "#" .. _skill.level .. " on " .. _target .. " (range " .. range .. ")");
        SkillObject(self.id, _skill.level, _skill.code, _target);
        return PROGRESS_SUCCESS;
      end
    end

    -- We're not in range or we can't attack yet.
    return PROGRESS_FAILURE;
  end

  return instance;
end



-- Move to a destination (not interruptible)
-- progress: progress state
-- id: who is responsible for the task
-- origin: where the task started
-- time: when the task started
-- hp: hit points at task creation
-- sp: skill points at task creation
-- target: desired target to follow
Go = {};

function Go.Create(_pet, _destination)
  local instance = Task.Create(_pet);

  instance.destination = _destination;

  --[[ Go to Destination ]]--
  function instance:Execute()
    Debug("Go.Execute()");

    -- Try to keep in range.
    return self:Progress(self:Move(self.destination));
  end

  return instance;
end



-- Skill Task (in-place, targeted, single strike) (not interruptible)
-- progress: progress state
-- id: who is responsible for the task
-- origin: where the task started
-- time: when the task started
-- hp: hit points at task creation
-- sp: skill points at task creation
-- resilience: modified resilience value
-- skill: any skill skill
-- target: desired skill target
Do = {};

function Do.Create(_pet, _skill, _target)
  local instance = Task.Create(_pet);

  instance.resilience = _pet:GetResilience();
  instance.skill = _skill;
  instance.target = _target;

  --[[ Attack one time ]]--
  function instance:Execute()
    Debug("Do.Execute()");

    local motion = GetV(V_MOTION, self.target);

    -- Don't target dead things.
    if ((motion == MOTION_DEAD) or (motion < 0)) then
      Info("Do: Target " .. self.target .. " is unavailable.");
      return self:Progress(PROGRESS_FAILURE);
    end

    return self:Progress(self:Target(self.skill, self.target));
  end

  return instance;
end
